﻿// Matt's TinyMVC Copyright (c) 2009 Matt J. Crouch

// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the "Software"), to deal in the Software without restriction, 
// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all copies
// or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;

namespace MattsTinyMVC
{
    [Serializable]
    public abstract class BaseModel
    {
        protected BaseModel()
        {
            _id = Guid.NewGuid().GetHashCode();

            _modelRemovals = new List<int>();
            _modelAdditions = new Dictionary<int, BaseModel>();

            foreach (FieldInfo observableCollection in GetTrackableCollections())
            {
                Type[] typeArgs = observableCollection.FieldType.GetGenericArguments();
                Type collectionType = typeof(ObservableCollection<>).MakeGenericType(typeArgs[0]);

                INotifyCollectionChanged collection = observableCollection.GetValue(this) as INotifyCollectionChanged;
                if (collection == null)
                {
                    collection = Activator.CreateInstance(collectionType) as INotifyCollectionChanged;
                    collection.CollectionChanged += new NotifyCollectionChangedEventHandler(collection_CollectionChanged);
                    observableCollection.SetValue(this, collection);
                }
            }
        }

        internal IEnumerable<FieldInfo> GetTrackableCollections()
        {
            return this.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic)
            .Where(field =>
                       field.GetCustomAttributes(typeof(TrackModelsAttribute), true).Count() > 0 &&
                        field.FieldType.IsGenericType ?
                        field.FieldType.GetGenericTypeDefinition() == typeof(ObservableCollection<>) :
                        field.FieldType == typeof(INotifyCollectionChanged));
        }

        private void collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            BaseModel controller = GetController(this);

            if (controller is BaseController)
            {
                if (controller._modelAdditions == null)
                    controller._modelAdditions = new Dictionary<int, BaseModel>();

                if (controller._modelRemovals == null)
                    controller._modelRemovals = new List<int>();

                switch (e.Action)
                {
                    case NotifyCollectionChangedAction.Add:
                        foreach (BaseModel newModel in e.NewItems)
                        {
                            controller.ModelAdditions.Add(newModel.ID, newModel);
                            newModel.Parent = this;
                        }
                        break;
                    case NotifyCollectionChangedAction.Remove:
                        foreach (BaseModel removedModel in e.OldItems)
                            controller.ModelRemovals.Add(removedModel.ID);
                        break;
                    default:
                        break;
                }
            }
        }

        private List<int> _modelRemovals;
        internal IList<int> ModelRemovals
        {
            get { return _modelRemovals; }
        }

        private Dictionary<int, BaseModel> _modelAdditions;
        internal IDictionary<int, BaseModel> ModelAdditions
        {
            get { return _modelAdditions; }
        }

        private int _id;
        public int ID
        {
            get { return _id; }
        }

        private BaseModel _parent;
        public BaseModel Parent
        {
            get { return _parent; }
            internal set { _parent = value; }
        }

        private BaseModel GetController(BaseModel model)
        {
            if (model.Parent == null)
                return model;

            BaseModel controller = GetController(model.Parent);
            if (controller.Parent == null)
                return controller;

            return null;
        }
    }
}